블랙 커피 프론트엔드 레벨 1 스터디 1주차 정리

여러가지로 바쁜 와중에 next step에서 진행하는 블랙 커피 자바스크립트 스터디에 참여하게 되었다!

원래는 cypress 테스트 코드 작성이 주로를 이루는 레벨2를 하고싶었지만, 바닐라 자바스크립트를 컴포넌트화해서 짜본 경험이 많지 않아서 너무 미숙한 탓에 과정을 못따라갈 것 같아서 레벨 1을 참여하게 되었고, 정말 잘 한 선택이라고 생각한다 😂

그래서 간단하지만 이번주차 미션을 진행하며 한 고민들, 배운 지식들을 남겨보고자 한다.

1 주차 미션 요구사항

  • 바닐라 자바스크립트로 투두리스트를 만든다.
  • 투두에는 투두 생성 / 삭제 / 수정 / 체크 기능이 있다.
  • 투두리스트 하단에 현재 화면에 보이는 투두의 갯수를 보여주어야 한다.
  • ‘전체보기’ , ‘해야할 일’, ‘한 일’ 상태에 따라 보여지는 투두가 달라야 한다.
  • 로컬스토리지에 투두를 저장해서 새로고침해도 투두 리스트가 유지 되어야 한다. (심화미션)

구현에 대한 고민 - 상태관리

구현에서 가장 중점적인 부분은 아래와 같았다.

  • 투두 작성시 투두 리스트에 제대로 추가 되어야 한다.
  • ‘전체보기’ , ‘해야할 일’, ‘한 일’ 상태를 변경하면 보여지는 투두리스트의 데이터를 변경해줘야 한다.
  • 현재 보여지는 투두리스트 데이터에 따라 하단에 렌더링 되는 투두의 갯수가 달라져야한다.

즉 가장 고민을 많이 했던 부분은 바닐라 자바스크립트에서 상태관리를 어떻게 할 것인가였다. 그렇다.. 리액트만 하다가 바닐라 자바스크립트를 건들이니 리액트가 제공해주는 편리한 상태관리의 소중함을 깨닫게 되었다. 그래서 생각이 아래까지 닿았다.

리액트라면 어떻게 했을까?

리액트에서라면 크게 두가지 방법이 있을 것 같다.

  1. Context API 나 리덕스에서 데이터베이스로부터 투두 리스트를 가져오고, 해당 데이터를 여러 컴포넌트에서 사용한다. 생성, 삭제 ,수정, 체크와 같은 데이터에 대한 연산도 스토어에 dispatch 해준다.
  2. Page 컴포넌트에서 투두리스트 데이터를 데이터베이스로부터 가져오고, 해당 데이터를 props 를 통해 여러 컴포넌트에 전달해준다. 생성, 삭제, 수정 , 체크와 같은 데이터 연산도 Page 컴포넌트에서 넘겨준다. 그래야만 데이터 변경이 모든 컴포넌트에 잘 반영되기 때문이다.

어쩌면 투두리스트와 같은 작은 어플리케이션에서는 2번 방법을 쓰는게 더 효율적이지 모르겠지만..이왕 해보는거 1번 방법을 자바스크립트로 구현해보고 싶었다.

구현 방법

먼저 전체적인 투두 리스트에는 옵저버 패턴(Observer Pattern) 을 적용하기로 했다. 옵저버 패턴..말로만 들어봤지, 이전에는 어떤 패턴인지 제대로 이해하지 못하고 있었는데 정말 이번 스터디로 인해 알아가는 지식이다!

옵저버 패턴은 간단하게 말하면 발행자구독자(관찰자) 로 나누어지고, 발행자 가 특정 상황에서 notify 를 날리면 해당 발행자에게 등록된 모든 구독자의 notify 함수가 실행된다.

예를들어, 구독중인 유튜브 채널이 새로운 동영상을 업로드 했을 때, 모든 구독자에게 알림이 오는 것과 같다고 생각하면 된다.

다만 이걸 객체객체의 관계로 바꾸는 것이다. 그리고 발행자가 notify 를 날렸을 때 구독자가 어떤 함수를 실행 할지는 우리가 정할 수 있다. 즉 notify 를 날렸을 때 알림을 띄워줄 수도 있고, 그냥 피드만 변경해줄 수도 있다. 이것 역시 발행자 측에서 구현이 가능하다.

그렇다면 코드를 통해 더 자세히 알아보겠다.

발행자

class Subject {
  constructor() {
    this.observers = []
  }
  // 옵저버 추가
  addObserver(observer) {
    this.observers = [...this.observers, observer]
  }

  // 옵저버 삭제
  removeObserver(observer) {
    const filteredList = this.observers.filter(v => v !== observer)
    this.observers = filteredList
  }

  // observers에 등록된 옵저버들의 update 함수 실행하여 상태 변경 notify
  notifyAll() {
    this.observers.forEach(observer => observer.update())
  }
}

export default Subject
  • 발행자 함수에서는 옵저버(구독자) 추가 / 삭제 / notify 메서드가 구현 되어있다.
  • notifyAll 을 하면 모든 옵저버의 특정 메서드를 실행하는 것인데, 여기서는 update 메서드를 실행한다. update 메서드는 렌더링을 새로 해주는 메서드로 구현 될 것이다.

관찰자(구독자)

class Observer {
  update() {}
}

export default Observer
  • 구독자에는 위에서 notify 시 실행할 공통 메서드만 작성해준다. 관찰자가 될 컴포넌트는 모두 다 다르기 때문이다. 이 메서드는 추후에 각기 다른 컴포넌트들이 오버라이딩(overriding) 하게 될 것이다.

TodoStore

위에 작성한 발행자 컴포넌트는 아래와 같이 TodoStore 컴포넌트에서 상속받게 된다. 복잡한 내용은 생략했다.

// Todo 앱 전반 State 관리
class TodoStore extends Subject {
  constructor(initialData) {
    super();
    this.originData = initialData; // 데이터베이스로부터 가져온 전체 데이터
    this.renderData = initialData; // 보여줄 데이터
    this.status = STATUS.ALL; // 투두 필터링 status
  }

  /**
   * @param {object[]} todoData
   */
  setOriginData(todoData) {
    // 로컬 스토리지에 저장
    localStorage.setItem(todoData);
    this.originData = todoData;
  }
  /**
   * @param {object[]} renderData
   */
  setRenderData(renderData) {
    this.renderData = renderData;
    this.notifyAll();
  }

  /**
   * @param {string} status
   */
  setStatus(status) {
   ....
  }
}

export default TodoStore;
  • 외부에서 setRenderData 혹은, setOriginData를 통해 렌더링되는 데이터를 변경시켜주면 상속받은 notifyAll 메서드를 실행하고, notifyAll 메서드는 현재 등록된 관찰자들에게 update 메서드를 실행시켜줌으로써 리렌더링을 시켜준다.

이렇게 구현하니, TodoStore의 변경된 데이터에 의해 리렌더링 되어야 할 컴포넌트 (TodoList, TodoCount) 들만 관찰자로 등록해주면되었다.

그 외에 나름 신경 쓴 부분

위에 TodoStore를 보면 originDatarenderData가 나누어져 있는데, 각각의 역할은 아래와 같다.

  • originData

    • 초기에 로컬스토리지(데이터베이스) 로부터 받아온 값을 저장한다. 원본데이터
    • setOriginData 를 통해 로컬스토리지에 있는 원본 데이터를 수정해준다. 이 때 렌더링 notify는 따로 안해준다.
  • renderData

    • 현재 status (‘전체’, ‘해야할 일’, ‘한 일’) 에 따라 렌더링될 데이터를 담고 있다.
    • setRenderData를 통해 렌더링 될 데이터를 수정해주고, 이 때 notify를 실행하여 관찰자 컴포넌트들을 리렌더링 해주어, 변경된 사항을 반영해준다.

만약 renderData와 originData를 하나의 State 로만 사용한다면 사용자가 status를 바꿀 때 마다 계속 데이터베이스에 요청을 보내야한다. 그래서 이 점이 비효율적인 것 같아서, 한번 나름대로 데이터베이스로의 요청을 최소화 시켜보고자 이렇게 구현해봤다 .. !


이런게 있었다니..?

  1. closest 셀렉터

    • 이런 셀렉터가 있었다..! 처음에는 parentNode를 타고 올라가서 원하는 엘리먼트를 찾았는데, 해당 셀렉터를 발견하고 매우 간편해졌다.
    • element.closest() 이렇게 사용해주면 된다
  2. dataset

    • 렌더링 되는 투두들은 모두 고유한 id 값을 가지고 있다. 그리고 삭제 , 수정 연산을 위해 id 값을 투두 엘리먼트에 기입해주어야 한다. 처음에는 그냥 id 값을 쓰려고 했으나 그러면 나중에 id를 추가할 때 덮어쓰여질 위험도 있고, 마크업 구조가 바뀌면 골치아파지겠다 싶어서…뭔가 다른 방법이 없을까 하다가 dataset을 발견했다.
    • Element.dataset.id 로 접근 가능하다!

코드 리뷰 받은 내용 정리

  • 주석은 꼭 설명이 필요한 경우에만 !
  • forEach 대신 map 사용하기

    • forEach는 기존의 배열 혹은 객체에 변경을 가하는 메서드이기 때문에 예상치 못한 데이터의 변경이 일어날 수 있다.
    • 데이터는 불변객체처럼 다루어주도록 하기!
  • 반복문 보다는 파이프
  • for 문 대신 배열 내장 메서드 사용하기

    • for 문을 위해서는 변수도 선언해주고, 변수도 계속 증감시켜주어야하고, 그 변수를 비교도 해주어야하기 때문에 실무에서는 안쓰인다고 한다.
  • document.querySelector 를 계속 쓰기보다는 함수로 생성해서 중복 줄이기

짤막한 회고

참여 계기 ?

개발관련 스터디는 학교에서 CS 동아리 내부에서 참여했던 스터디를 제외하고 해본적이 없었는데 이번에 이렇게 참여하게 된 가장 큰 계기는 성장에 대한 욕심 때문이다!

독학을 해오다보니까 내 코드가 괜찮은 코드인가 ? 내가 잘하고 있는가 ? 에 대한 명쾌한 답을 얻기가 힘들었고, 요즘따라 성장에 대한 지체를 극심하게 느끼게 되었다.

그러다보니 쉽게 지치게 되었고, 그래서 다른 사람들과 커뮤니케이션을 통해 성장하고 싶어서 드릉드릉 했던 찰나에 블랙 커피 스터디를 발견하게 되었고 망설임 없이 참여하게 되었다.


1주차를 지나고

1주차를 지나고 든 생각은 정말 하기 잘했다! 라는 것이다.

일단 만천하에 나의 코드를 공개해야한다는 생각에 의해서 정말 코드를 정성들여서 읽기 쉽게 작성하게 되었고, 이 부담감이 기존의 나의 마인드 셋.. 기능하게 만들고 그 후에 리팩토링 하자을 많이 바꾸어 준 것 같다. (이제는 버려야할 나쁜 마인드셋 👋 )

그리고 나에게는 데드라인이 정말 필요하다는 것을 느끼기도 했다. 명확한 데드라인이 주어지니 강제로 일정 및 시간관리를 할 수 밖에 없었고, 복잡하지 않은 기능이지만 기능을 나름 빨리 구현해 낼 수 있어서 정말 뿌듯 했다.


첫 코드 리뷰

나는 이전에 코드리뷰를 받아본적도 해본적도 없다. (셀프 코드리뷰는 해본적 있다 😭…혼자 풀리퀘스트 열고 코드리뷰 했었음.. )

이번 기수부터는 온라인 세션시간 동안 두분의 코드를 리뷰하게 되었고 리뷰를 하는 동안 다른 분들의 코드를 읽으며 내가 생각지 못했던 설계방법이나, 여러 좋은 코드 그리고 아쉬운 점을 발견하면서 한 걸음 더 성장한 것 같았다.

반대로, 나 역시도 코드리뷰를 받았는데, 내가 사소하게 놓쳤던 부분이나, 정말 알지 못했던 부분들을 깨달을 수 있었다 👏



다음주차에 대한 기대

코드리뷰하고 다른 팀원분들과 회고하는 시간이 굉장히 재미있었다. 물론 첫 회고라 뭔가 어색어색한 점은 있었지만 그래도 같은 관심사를 가지고 있고, 같은 미션을 해결해나가는 분들과 커뮤니케이션 하는 것 자체가 매우 즐거웠다 😃

그래서 다음 주에도 미션을 잘 구현해내고 다른 분들과 함께 커뮤니케이션하며 더 성장하고싶다. 다음주 이 시간에는 얼만큼 성장해있을까 ? 벌써부터 기대된다


This is@moonee
프론트엔드 개발 공부 블로그

GitHub